/*
 *The code is written by NaLi, All rights reserved.
 */
package com.nali.dal.statement.query;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.nali.dal.expression.query.Criteria;
import com.nali.dal.expression.query.CriteriaType;
import com.nali.dal.statement.function.DataHandler;
import com.nali.dal.statement.function.DataHandlerFactory;
import com.nali.dal.statement.function.Function;
import com.nali.dal.statement.function.FunctionArgument;
import com.nali.dal.statement.function.FunctionBuilder;
import com.nali.dal.statement.function.FunctionException;

/**
 * @author gavin 
 * Created on 2011-4-8
 */
@Component
public class ColumnUnitFactory {
	private static final Pattern aliasSpliter = Pattern.compile("(?i)\\s+as\\s*");
	private static Map<String, Criteria> groupFunctions;
	@Autowired
	private DataHandlerFactory dataHandlerFactory;
	static {
		groupFunctions = new HashMap<String, Criteria>();
		Criteria[] criterias = Criteria.values();
		for (Criteria criteria : criterias) {
			if(criteria.type.equals(CriteriaType.groupFunction)) {
				groupFunctions.put(criteria.name(), criteria);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	public ColumnUnitConfig compile(String input) {
		String[] values = aliasSpliter.split(input.trim(), 2);
		String alias = null;
		String exp = null;
		if(values.length == 2) {
			exp = values[0];
			alias = values[1];
		}else{
			exp = values[0];
		}
		if(StringUtils.isEmpty(exp)) {
			throw new IllegalArgumentException("Expression is null or empty!");
		}
		if(StringUtils.isEmpty(alias)) {
			alias = exp;
		}
		
		Function function = FunctionBuilder.complie(exp);
		if(function.isFunction()) {
			String orgFunctionName = function.getName();
			String functionName;
			boolean isConstant;
			if(orgFunctionName.startsWith("$")) {
				isConstant = true;
				functionName=orgFunctionName.substring(1);
			} else {
				isConstant = false;
				functionName=orgFunctionName;
			}
			DataHandler<Object> handler = null;
			if(functionName.indexOf('.')!=-1) {
				try {
					handler = (DataHandler<Object>) Class.forName(functionName).newInstance();
				} catch (Exception e) {
					throw new IllegalArgumentException(orgFunctionName + " is not a DataHandler",e);
				}
			} else {
				handler = dataHandlerFactory.getDataHandler(functionName);
				if(handler==null) {
					Criteria groupFunction = groupFunctions.get(functionName);
					if(groupFunction==null) {
						throw new IllegalArgumentException(orgFunctionName + " is not function");
					}
					if(isConstant) {
						throw new IllegalArgumentException("groupFunction cannot be eval to a constant:" + input);
					}
					return new GroupColumn(alias, groupFunction, function.getArgConfigs().get(0).getColumn());
				}
			}
			List<FunctionArgument> arguments = function.getArgConfigs();
			handler.validate(arguments);
			if(isConstant) {
				List<Object> invokeArgs=getArgList(arguments, input);
				return new ConstantColumn(alias, handler.handle(invokeArgs));
			} else {
				List<String> columns = function.getColumns();
				if(columns.size()==0) {
					List<Object> invokeArgs=getArgList(arguments, input);
					return new SimpleStatefulColumn(alias, function.getName(), dataHandlerFactory, invokeArgs);
				} else {
					return new SimpleColumn(columns, alias, function, handler);
				}
			}
		} else {
			List<FunctionArgument> arguments = function.getArgConfigs();
			if(arguments.size()!=1) {
				throw new FunctionException("arguments' size must be one:" + input);
			}
			FunctionArgument argument = arguments.get(0);
			if(argument.isColumn()) {
				return new OriginColumn(alias, argument.getColumn());
			} else {
				return new ConstantColumn(alias, argument.getConstant());
			}
		}
	}

	private List<Object> getArgList(List<FunctionArgument> arguments, String input) {
		List<Object> invokeArgs=new ArrayList<Object>(arguments.size());
		for (FunctionArgument functionArgument : arguments) {
			if(functionArgument.isConstant()) {
				invokeArgs.add(functionArgument.getConstant());
			} else {
				throw new IllegalArgumentException(input + " contain a column:" + functionArgument.getColumn());
			}
		}
		return invokeArgs;
	}
}

